package engine

import (
	"errors"
	"fmt"

	"gitee.com/sansaniot/ssiot-core/facade/env"
	"github.com/bytedance/sonic"
	js "github.com/dop251/goja"

	"ssdevice/common/constant"
	"ssdevice/common/utils"
	"ssdevice/internal/data/action"
	"ssdevice/internal/rule/engine/device"
	"ssdevice/internal/rule/engine/transform"
	"ssdevice/internal/rule/model"
)

/*
	规则引擎的处理器分为定时触发节点、设备数据触发节点。
	定时触发节点按schedule设置定时执行，设备数据上报时不执行，并通过sensor返回值入库或传递到下一个节点，直到遇到第二个定时处理器终止，第二个处理器也会在其它任务中定时执行
	设备触发节点在设备数据上报时执行
*/

func ExecDevDataTask(sensor map[string]interface{}, sn string, msgType string) map[string]interface{} {
	ruleId, processors := GetDevRule(sn)
	ruledCnt := len(processors)
	if ruledCnt == 0 {
		ruleId, processors = GetProductRule(sn)
	}
	ruledCnt = len(processors)
	if ruledCnt == 0 {
		return sensor
	} else {
		defer devRuleStats(ruleId, ruledCnt)
	}

	context := map[string]interface{}{
		constant.ContextSn:                   sn,
		constant.ContextMsgType:              msgType,
		constant.ContextRuleId:               ruleId,
		constant.ContextProcessorTriggerType: constant.RuleTriggerDevData,
	}

	// 执行处理器
	var result map[string]interface{}
	var err error

	for _, p := range processors {
		// 只执行非定期任务的节点
		if p["trigger"] == constant.RuleTriggerSchedule {
			continue
		}

		result, err = RunScript(p["script"], sensor, context)

		sensor = result
		if err != nil {
			env.Log.Errorf("rule-engine: sn %s, processor %s, run script err %s", sn, p["id"], err)
			break
		}

		env.Log.Debugf("rule-engine: processor %s with trigger %s execute completed . %v", p["id"], p["trigger"], sensor)
	}

	return sensor
}

func ExecScheduleTask(processor *model.Processor, sns []string) {
	// 每个设备单独处理
	for _, sn := range sns {
		context := map[string]interface{}{
			constant.ContextRuleId:               processor.RuleId,
			constant.ContextProcessorTriggerType: constant.RuleTriggerSchedule,
			constant.ContextSn:                   sn,
		}

		// 定时处理器的下级非定时处理器串行处理，如果下级有定时处理器，则是在另外的任务中执行
		procModel := &model.Processor{}
		procModel.Orm = env.Db
		processors := procModel.FindChainsByRule(processor.RuleId)
		sensor := make(map[string]interface{})
		var err error

		scheduleCount := 0

		for _, p := range processors {
			if p["trigger"] == constant.RuleTriggerSchedule {
				scheduleCount++
			}

			// 找到当前的定时触发器，并执行该处理器和该处理器后的设备数据触发器，直到下一个定时触发器时停止
			if p["id"] != processor.Id {
				continue
			}

			if scheduleCount > 1 {
				break
			}

			sensor, err = RunScript(p["script"].(string), sensor, context)

			if err != nil {
				env.Log.Errorf("rule-engine: processor %s run script err %s", p["id"], err)
				break
			}

			env.Log.Debugf("rule-engine: processor %s with trigger %s execute completed . %v", p["id"], p["trigger"], sensor)
		}

		if sensor == nil {
			continue
		}

		sensorAction := action.Sensor{}
		sensorAction.Orm = env.Db
		now := utils.GetNowTimeStr()
		sensorAction.SensorUpdate(action.SensorMsg{
			DevSn:  sn,
			Sensor: sensor,
			Now:    now,
		})
		action.UpdateLiveStatus(env.Db, sn, sn, now)
	}
}

func devRuleStats(ruleId string, ruledCnt int) {
	if tmpCount, ok := constant.DevRuleCountCache.Get(ruleId); !ok {
		constant.DevRuleCountCache.Set(ruleId, ruledCnt)
	} else {
		constant.DevRuleCountCache.Set(ruleId, tmpCount+ruledCnt)
	}
}

func GetDevRule(sn string) (id string, processors []map[string]string) {
	// 设备或产品是否存在规则
	cacheKey := fmt.Sprintf("%s:%s", model.RuleCacheKeyPrefix, sn)
	ruleInfo, err := env.Cache.HashGetAll(cacheKey)
	id, _ = ruleInfo["id"]
	rule, _ := ruleInfo[model.RuleChainKey]

	if err != nil {
		return "", nil
	}

	processors = make([]map[string]string, 0)
	err = sonic.Unmarshal([]byte(rule), &processors)
	if err != nil {
		return "", nil
	}

	return id, processors
}

func GetProductRule(sn string) (id string, processors []map[string]string) {
	//查询设备对应产品编码
	devCache, _ := env.Cache.HashGet(constant.DeviceEasyCache, sn)
	if len(devCache) == 0 {
		return "", nil
	}
	info := utils.BytesToMap([]byte(devCache))
	productModel := utils.If(info["productModel"] != nil, info["productModel"], "").(string)
	if len(productModel) == 0 {
		return "", nil
	}
	// 设备或产品是否存在规则
	cacheKey := fmt.Sprintf("%s:%s", model.RuleProductCacheKeyPrefix, productModel)
	ruleInfo, err := env.Cache.HashGetAll(cacheKey)
	id, _ = ruleInfo["id"]
	rule, _ := ruleInfo[model.RuleChainKey]

	if err != nil {
		return "", nil
	}

	processors = make([]map[string]string, 0)
	err = sonic.Unmarshal([]byte(rule), &processors)
	if err != nil {
		return "", nil
	}

	return id, processors
}

// RunScript 应用规则
func RunScript(script string, sensor map[string]interface{}, context map[string]interface{}) (result map[string]interface{}, err error) {
	//脚本例子
	const SCRIPT = `
	function f(sensor, context) {
        var result = {};
         sensor.b = sensor.a + 2;
         sensor.c = sensor.a + 3;
		return sensor;
	}
	`
	//初始化js引擎
	vm := js.New()
	vm.Set("Transform", transform.TransFuncRegister)
	vm.Set("Device", device.DevFuncRegister)

	//运行js,验证语法是否错误
	_, err = vm.RunString(script)
	if err != nil {
		return nil, err
	}

	fns, ok := js.AssertFunction(vm.Get("f"))
	if !ok || fns == nil {
		return nil, errors.New("illegal function")
	}

	//导出js函数
	var fn func(map[string]interface{}, map[string]interface{}) (map[string]interface{}, error)
	err = vm.ExportTo(vm.Get("f"), &fn)
	if err != nil {
		return nil, errors.New("export function fail")
	}

	//执行函数获取返回值
	result, err = fn(sensor, context)
	if err != nil {
		return nil, errors.New(fmt.Sprintf("execute function error %s", err))
	}

	return result, nil
}
